/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Windows.Data;
using System.Linq;
using System.Collections.Generic;

namespace Xceed.Silverlight.Data
{
  internal class GroupHandler : HandlerBase
  {
    public GroupHandler( IDataEnumerable<DataPath> allData )
    {
      m_groupDescriptions.CollectionChanged += new NotifyCollectionChangedEventHandler( GroupDescriptionsCollectionChanged );
      m_dataSource = allData;
      m_publicCollapsedGroupCollections = new CollapsedGroupCollection( m_collapsedGroupCollections );
    }


    #region GroupHeadersFootersConfigurationProvider

    public IGroupHeadersFootersSelector GroupHeadersFootersSelector
    {
      get;
      set;
    }

    #endregion GroupHeadersFootersConfigurationProvider

    #region GroupStatFunctionsSelector

    public IGroupStatFunctionsSelector GroupStatFunctionsSelector
    {
      get;
      set;
    }

    #endregion GroupStatFunctionsSelector

    public ObservableCollection<GroupDescription> GroupDescriptions
    {
      get
      {
        return m_groupDescriptions;
      }
    }

    public CollapsedGroupCollection CollapsedGroupCollections
    {
      get
      {
        return m_publicCollapsedGroupCollections;
      }
    }

    public bool HasHeadersFooters
    {
      get
      {
        if( ( this.GroupHeadersFootersSelector == null )
          || ( this.GroupDescriptions.Count == 0 ) )
          return false;

        for( int i = 0; i < this.GroupDescriptions.Count; i++ )
        {
          DataPath levelPath = new DataPath( Enumerable.Repeat( GroupDataItem.Any, i + 1 ).ToArray() );

          if( this.GroupHeadersFootersSelector.HasHeaders( levelPath )
            || this.GroupHeadersFootersSelector.HasFooters( levelPath ) )
            return true;
        }

        return false;
      }
    }

    public bool HasCollapsedGroups
    {
      get
      {
        return m_collapsedGroupCollections.Any(
          ( collection ) => collection.ContainsItems || collection.IsReversed
          );
      }
    }

    internal IEnumerable<string> PropertyNames
    {
      get
      {
        return m_groupDescriptions.GetPropertyNames();
      }
    }

    internal IEnumerable<ListSortDirection> SortDirections
    {
      get
      {
        return m_groupDescriptions.Select( ( group ) => ListSortDirection.Ascending );
      }
    }

    internal GroupCountPolicy GroupCountPolicy
    {
      get
      {
        return m_groupCountPolicy;
      }
      set
      {
        m_groupCountPolicy = value;
      }
    }

    internal bool IncludeGroupItemsCount
    {
      get
      {
        return m_includeGroupItemsCount;
      }
      set
      {
        m_includeGroupItemsCount = value;
      }
    }

    internal bool IncludeGroupItems
    {
      get
      {
        return m_includeGroupItems;
      }
      set
      {
        m_includeGroupItems = value;
      }
    }

    public event EventHandler<ExpansionChangedEventArgs> ExpansionChanged;

    internal event EventHandler<ExpansionChangedEventArgs> InternalExpansionChanging;

    internal event EventHandler<ExpansionChangedEventArgs> InternalExpansionChanged;

    public bool IsCollapsed( DataPath groupPath )
    {
      if( groupPath == null )
        throw new ArgumentNullException( "groupPath" );

      if( !( groupPath.LastChild is GroupDataItem ) )
        throw new ArgumentException( "The specified path must refer to a GroupDataItem instance.", "groupPath" );

      if( groupPath.Depth > m_collapsedGroupCollections.Count )
        throw new ArgumentException( "The specified groupPath is invalid.", "groupPath" );

      return m_collapsedGroupCollections[ groupPath.Depth - 1 ].Contains( groupPath );
    }

    public DataPath GetGroupingPath( DataPath completePath )
    {
      int groupingDepth = this.GetGroupingDepth( completePath );

      return (groupingDepth == completePath.Depth)
        ? completePath
        : completePath.CreateAncestorPath( groupingDepth );
    }

    internal int GetGroupingDepth( DataPath completePath )
    {
      if( completePath == null )
        throw new ArgumentNullException( "completePath" );

      int groupingDepth = 0;

      while( groupingDepth < completePath.Depth )
      {
        if( !( completePath[ groupingDepth ] is GroupDataItem ) )
          break;

        groupingDepth++;
      }

      return groupingDepth;
    }

    internal int GetCollapsedDepth( DataPath path )
    {
      if( path == null )
        throw new ArgumentNullException( "path" );

      int collapsedDepth = 0;
      for( int i = 0; ( i < m_collapsedGroupCollections.Count ) && ( i < path.Depth ); i++ )
      {
        if( !( path[ i ] is GroupDataItem ) )
          break;

        if( m_collapsedGroupCollections[ i ].IsAncestorCollapsed( path ) )
        {
          collapsedDepth = i + 1;
          break;
        }
      }

      return collapsedDepth;
    }

    internal IAsyncEnumerator<object> GetHeadersEnumerator( DataPath groupPath )
    {
      return this.GetHeadersFootersEnumerator( groupPath, true );
    }

    internal IAsyncEnumerator<object> GetFootersEnumerator( DataPath groupPath )
    {
      return this.GetHeadersFootersEnumerator( groupPath, false );
    }

    internal IAsyncEnumerator<DataPath> GetHeadersDataPathEnumerator( DataPath groupPath )
    {
      return this.ConvertEnumerator( groupPath, HeaderFooterType.Header, this.GetHeadersFootersEnumerator( groupPath, true ) );
    }

    internal IAsyncEnumerator<DataPath> GetFootersDataPathEnumerator( DataPath groupPath )
    {
      return this.ConvertEnumerator( groupPath, HeaderFooterType.Footer, this.GetHeadersFootersEnumerator( groupPath, false ) );
    }

    private IAsyncEnumerator<DataPath> ConvertEnumerator( DataPath groupPath, HeaderFooterType headerType, IAsyncEnumerator<object> objectHeadersFootersEnum )
    {
      return new ConversionEnumerator<DataPath>(
        objectHeadersFootersEnum,
        ( headerFooterObject ) => groupPath.CreateChildPath( new HeaderFooterDataItem( headerFooterObject, headerType ) ),
        ( headerFooterPath ) => ( ( HeaderFooterDataItem )headerFooterPath.LastChild ).Data );
    }

    private IAsyncEnumerator<object> GetHeadersFootersEnumerator( DataPath groupPath, bool headers )
    {
      if( groupPath == null )
        throw new ArgumentNullException( "groupPath" );

      if( !( groupPath.LastChild is GroupDataItem ) )
        throw new ArgumentException( "path must refer to a group", "groupPath" );

      IEnumerable<object> headersFooters = null;

      if( this.GroupHeadersFootersSelector != null )
      {
        if( headers )
        {
          headersFooters = this.GroupHeadersFootersSelector.GetHeaders( groupPath );
        }
        else
        {
          headersFooters = this.GroupHeadersFootersSelector.GetFooters( groupPath );
        }
      }

      return new SyncToAsyncEnumerator<object>(
        new LocalListEnumerator<object>( ( headersFooters == null ) ? new List<object>() : headersFooters.ToList() ) );
    }

    private void GroupDescriptionsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      if( e.NewItems != null )
      {
        PropertyGroupDescription propertyGroupDescription = e.NewItems[ 0 ] as PropertyGroupDescription;
        DataGroupDescription dataGroupDescription = e.NewItems[ 0 ] as DataGroupDescription;
        if( propertyGroupDescription != null )
        {
          if( propertyGroupDescription.Converter != null )
            throw new InvalidOperationException( "IValueConverters are not supported when using a virtualized data source." );
        }
        else if( dataGroupDescription != null )
        {
          if( dataGroupDescription.SortComparer != null )
            throw new InvalidOperationException( "SortComparers are not supported when using a virtualized data source." );
        }
        else
        {
          throw new InvalidOperationException( "Only PropertyGroupDescription and DataGroupDescription instances are supported." );
        }
      }

      // Clear all collapsed paths.
      foreach( GroupCollection groupCollection in m_collapsedGroupCollections )
      {
        groupCollection.SubCollectionChanged -= new EventHandler<SubCollectionChangedEventArgs>( CollapsedGroupCollectionChanged );
        groupCollection.SubCollectionChanging -= new EventHandler<SubCollectionChangedEventArgs>( CollapsedGroupCollectionChanging );
      }

      m_collapsedGroupCollections.Clear();

      for( int i = 0; i < m_groupDescriptions.Count; i++ )
      {
        GroupCollection groupCollection = new GroupCollection( m_dataSource );
        groupCollection.SubCollectionChanged += new EventHandler<SubCollectionChangedEventArgs>( CollapsedGroupCollectionChanged );
        groupCollection.SubCollectionChanging += new EventHandler<SubCollectionChangedEventArgs>( CollapsedGroupCollectionChanging );
        m_collapsedGroupCollections.Add( groupCollection );
      }
      this.RaiseSourceChanged( new SourceChangedEventArgs( SourceChangedType.GroupingChanged ) );
    }

    private void GroupHeadersFootersCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      this.RaiseSourceChanged( new SourceChangedEventArgs( SourceChangedType.GroupingChanged ) );
    }

    private void CollapsedGroupCollectionChanging( object sender, SubCollectionChangedEventArgs e )
    {
      if( this.InternalExpansionChanging != null )
      {
        this.InternalExpansionChanging( this, this.ConvertEvent( ( GroupCollection )sender, e ) );
      }
    }

    private void CollapsedGroupCollectionChanged( object sender, SubCollectionChangedEventArgs e )
    {
      if( this.InternalExpansionChanged != null || this.ExpansionChanged != null )
      {
        ExpansionChangedEventArgs args = this.ConvertEvent( ( GroupCollection )sender, e );
        if( this.InternalExpansionChanged != null )
        {
          this.InternalExpansionChanged( this, args );
        }
        if( this.ExpansionChanged != null )
        {
          this.ExpansionChanged( this, args );

        }
      }
    }

    private ExpansionChangedEventArgs ConvertEvent( GroupCollection sender, SubCollectionChangedEventArgs args )
    {
      ExpansionChangedEventArgs newArgs;
      int collapsedDepth = m_collapsedGroupCollections.IndexOf( sender ) + 1;

      if( collapsedDepth <= 0 )
        throw new DataInternalException();

      switch( args.Action )
      {
        case SubCollectionChangedAction.Add:
          newArgs = new ExpansionChangedEventArgs( ExpansionAction.Collapse, ( DataPath )args.Target );
          break;
        case SubCollectionChangedAction.Clear:
          newArgs = new ExpansionChangedEventArgs( ExpansionAction.ExpandAll, collapsedDepth );
          break;
        case SubCollectionChangedAction.IncludeAll:
          newArgs = new ExpansionChangedEventArgs( ExpansionAction.CollapseAll, collapsedDepth );
          break;
        case SubCollectionChangedAction.Remove:
          newArgs = new ExpansionChangedEventArgs( ExpansionAction.Expand, ( DataPath )args.Target );
          break;
        default:
          throw new ArgumentException( "The specified action is not supported.", "action" );
      }
      return newArgs;
    }

    private ObservableCollection<GroupDescription> m_groupDescriptions = new ObservableCollection<GroupDescription>();
    private List<GroupCollection> m_collapsedGroupCollections = new List<GroupCollection>();
    private CollapsedGroupCollection m_publicCollapsedGroupCollections;
    private IDataEnumerable<DataPath> m_dataSource;//null
    private GroupCountPolicy m_groupCountPolicy = GroupCountPolicy.DoNotHandleCounts;
    private bool m_includeGroupItemsCount = false;
    private bool m_includeGroupItems = false;
  }
}
